/**
 * <AiRmob-Framework; A simple augmented reality framework for android >
    Copyright (C) <2012>  <AiRmob Team>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package rendercomponents;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.StringTokenizer;

import airmob.framework.AiRmobFileManager;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import rendercomponents.Renderer;

public class ResourceManager implements ResourceManagerInterface {
	LinkedList<Texture> loadedTextures;
	LinkedList<VertexShader> loadedVertexShaders;
	LinkedList<FragmentShader> loadedFragmentShaders;
	LinkedList<ModelCustom> loadedModelCustom;
	LinkedList<ModelGroup> loadedModelGroups;

	private static final int FLOAT_SIZE_BYTES = 4;
	private static final int SHORT_SIZE_BYTES = 2;
	private static final int NORMAL_OFFSET = 3;
	private static final int TEXTURE_OFFSET = 6;

	// Used Renderer
	Renderer renderer;

	// ////////////////////////////////////////////////////////
	// /Constructors
	// ////////////////////////////////////////////////////////

	public ResourceManager(Renderer r) {
		this.renderer = r;
		this.loadedTextures = new LinkedList<Texture>();
		this.loadedVertexShaders = new LinkedList<VertexShader>();
		this.loadedFragmentShaders = new LinkedList<FragmentShader>();
		this.loadedModelCustom = new LinkedList<ModelCustom>();
		this.loadedModelGroups = new LinkedList<ModelGroup>();
	}

	public ResourceManager() {

	}

	// ////////////////////////////////////////////////////////
	// /Loads a shader. The type of the shader (vertex-/fragmentshader) and the
	// sourcepath must be given
	// ////////////////////////////////////////////////////////

	private int loadShader(int shaderType, String source) {
		int shader = GLES20.glCreateShader(shaderType);
		if (shader != 0) {
			GLES20.glShaderSource(shader, source);
			GLES20.glCompileShader(shader);
			int[] compiled = new int[1];
			GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
			if (compiled[0] == 0) {
				GLES20.glDeleteShader(shader);
				shader = 0;
			}
		}
		return shader;
	}

	// ////////////////////////////////////////////////////////
	// /Creates a program out of a vertex- and a fragmentshader, ready to use
	// for openGL and returns it id.
	// ////////////////////////////////////////////////////////

	/**
	 * Imports a vertex- and a fragmentshader and links them together as a
	 * program. The OpenGL ID of this program will be returned.
	 * 
	 * @param vertexShader
	 *            VertexShader that shall be used to create program.
	 * @param fragmentShader
	 *            FragmentShader that shall be used to create program.
	 */
	public int createProgram(VertexShader vertexShader,
			FragmentShader fragmentShader) {
		String vertexShaderSource = null;
		String fragmentShaderSource = null;
		String vertexShaderPath = vertexShader.getPath();
		String fragmentShaderPath = fragmentShader.getPath();

		boolean vertexFound = false;
		boolean fragmentFound = false;

		for (VertexShader vs : loadedVertexShaders) {
			if (vs.getPath().equals(vertexShader.getPath())) {
				vertexShader.ID = vs.ID;
				vertexShader.setPath(vs.getPath());
				vertexFound = true;
				System.out
						.println("VertexShader "
								+ vs.getPath()
								+ " already loaded, returning reference to this VertexShader.");
			}
		}

		for (FragmentShader fs : loadedFragmentShaders) {
			if (fs.getPath().equals(fragmentShader.getPath())) {
				fragmentShader.ID = fs.ID;
				fragmentShader.setPath(fs.getPath());
				fragmentFound = true;
				System.out
						.println("FragmentShader "
								+ fs.getPath()
								+ " already loaded, returning reference to this FragmentShader.");
			}
		}

		if (!vertexFound) {
			try {
				vertexShaderSource = AiRmobFileManager.readInternFile(
						renderer.mContext, vertexShaderPath);
			} catch (IOException e) {
				e.printStackTrace();
			}

			vertexShader.ID = loadShader(GLES20.GL_VERTEX_SHADER,
					vertexShaderSource);
			if (vertexShader.ID == 0) {
				return 0;
			}
		}

		if (!fragmentFound) {
			try {
				fragmentShaderSource = AiRmobFileManager.readInternFile(
						renderer.mContext, fragmentShaderPath);
			} catch (IOException e) {
				e.printStackTrace();
			}
			fragmentShader.ID = loadShader(GLES20.GL_FRAGMENT_SHADER,
					fragmentShaderSource);
			if (fragmentShader.ID == 0) {
				return 0;
			}
		}

		int program = GLES20.glCreateProgram();
		if (program != 0) {
			GLES20.glAttachShader(program, vertexShader.ID);
			GLES20.glAttachShader(program, fragmentShader.ID);
			GLES20.glLinkProgram(program);
			int[] linkStatus = new int[1];
			GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
			if (linkStatus[0] != GLES20.GL_TRUE) {
				GLES20.glDeleteProgram(program);
				program = 0;
			}
		}

		loadedVertexShaders.add(vertexShader);
		loadedFragmentShaders.add(fragmentShader);

		return program;
	}

	// ////////////////////////////////////////////////////////
	// /Loads an model from an .obj-File. If there are more than one model in
	// the .obj-File they will
	// /be merged together with only 1 material, texture and bumpmap
	// ////////////////////////////////////////////////////////

	public boolean loadModelFromOBJ(String resource, ModelCustom model,
			boolean useVBO) {

		for (ModelCustom m : loadedModelCustom) {
			if (m.path.equals(resource)) {
				System.out
						.println("ModelCustom "
								+ resource
								+ " allready loaded. Duplicate all fields. Better use identifiers for multiple instances.");
				model.mesh = m.mesh;
				model.iBuffer = m.iBuffer;
				model.setTransparent(m.isTransparent());
				model.bIndexBuffer = m.bIndexBuffer;
				model.bumpMap = m.bumpMap;
				model.ambient = m.ambient;
				model.diffuse = m.diffuse;
				model.name = m.name;
				model.normalsSet = m.normalsSet;
				model.numIndices = m.numIndices;
				model.path = m.path;
				model.specular = m.specular;
				model.specular_exponent = m.specular_exponent;
				model.texture = m.texture;
				model.textureCoordsSet = m.textureCoordsSet;
				model.textureSet = m.textureSet;
				return true;
			}
		}

		loadedModelCustom.add(model);

		try {

			AssetManager assetManager = renderer.mContext.getAssets();
			InputStream inputStream = assetManager.open(resource);

			BufferedReader in = new BufferedReader(new InputStreamReader(
					inputStream));

			LinkedList<String> names = new LinkedList<String>();
			LinkedList<float[]> ambient = new LinkedList<float[]>();
			LinkedList<float[]> diffuse = new LinkedList<float[]>();
			LinkedList<float[]> specular = new LinkedList<float[]>();
			LinkedList<float[]> exponent = new LinkedList<float[]>();
			LinkedList<String> texPaths = new LinkedList<String>();
			LinkedList<String> bumpPaths = new LinkedList<String>();
			LinkedList<String> mapnsPaths = new LinkedList<String>();

			String str = in.readLine();

			// Skip all comments and blank lines
			while (str.length() == 0 || str.charAt(0) == '#') {
				str = in.readLine();
				if (str == null)
					return false;
			}

			boolean materialImported = false;

			if (str.charAt(0) == 'm') {
				// Get the material path
				String target = resource;

				int pos = 0;
				for (int i = 0; i < resource.length(); i++) {
					if (resource.charAt(i) == '/') {
						pos = i;
					}
				}

				String path = resource.substring(0, pos);

				String materialPath = path + "/" + str.substring(7);

				// System.out.println(materialPath);
				this.loadMaterialMTL(materialPath, names, ambient, diffuse,
						specular, exponent, texPaths, bumpPaths, mapnsPaths,
						target);
				
				materialImported = true;

				str = in.readLine();
			}

			if (str.charAt(0) == 'o' || str.charAt(0) == 'g') {
				// Get the object name
				String objectName = str.substring(2);
				// System.out.println(objectName);
				model.name = objectName;
			}

			// skip everything until first vertex occures
			while (str.charAt(0) != 'v') {
				// get the next line
				str = in.readLine();
				if (str == null)
					return false;
			}

			// lists to store all vertices, textureCoordinates and normals
			LinkedList<Float> vertexList = new LinkedList<Float>();
			LinkedList<Float> textureCoordinateList = new LinkedList<Float>();
			ArrayList<Float> normalList = new ArrayList<Float>(0);

			StringTokenizer token = new StringTokenizer(str);
			String type = token.nextToken();

			int numVertices = 0;
			while (type.equals("v")) {
				// Add x, y, z to vertexList
				vertexList.add(Float.parseFloat(token.nextToken()));
				vertexList.add(Float.parseFloat(token.nextToken()));
				vertexList.add(Float.parseFloat(token.nextToken()));

				// get the next line
				str = in.readLine();
				if (str.length() == 0)
					break;

				// put the fist token in token
				token = new StringTokenizer(str);
				type = token.nextToken();
				numVertices++;
			}

			// Skip all comments and blank lines
			while (str.length() == 0 || str.charAt(0) == '#') {
				str = in.readLine();
				if (str == null)
					return false;
			}

			token = new StringTokenizer(str);
			type = token.nextToken();

			int numTexCoords = 0;
			if (type.equals("vt")) {

				// Model uses texture
				model.textureCoordsSet = true;

				while (type.equals("vt")) {

					// Add u, v to textureCoordinateList
					textureCoordinateList.add(Float.parseFloat(token
							.nextToken()));
					textureCoordinateList.add(Float.parseFloat(token
							.nextToken()));

					// get the next line
					str = in.readLine();
					if (str.length() == 0)
						break;

					token = new StringTokenizer(str);
					// put the first token in token
					type = token.nextToken();
					numTexCoords++;

				}
			}

			// Skip all comments and blank lines
			while (str.length() == 0 || str.charAt(0) == '#') {
				str = in.readLine();
				if (str == null)
					return false;
			}

			token = new StringTokenizer(str);
			// put the first token in token
			type = token.nextToken();

			if (type.equals("vn")) {

				model.normalsSet = true;

				while (type.equals("vn")) {
					// Add nx, ny, nz to normalList
					normalList.add(Float.parseFloat(token.nextToken()));
					normalList.add(Float.parseFloat(token.nextToken()));
					normalList.add(Float.parseFloat(token.nextToken()));

					// get the next line
					str = in.readLine();
					if (str.length() == 0)
						break;

					token = new StringTokenizer(str);
					// put the first token in token
					type = token.nextToken();
				}
			}

			// Skip all comments and blank lines
			while (str.length() == 0 || str.charAt(0) == '#') {
				str = in.readLine();
				if (str == null)
					return false;
			}

			// If there are texturecoords set the imported materialvalues

			// Material for this model (includes texture, bumpmap texture, etc.)
			if (model.textureCoordsSet) {
				if (texPaths.size() == 0)
					System.out
							.println("Texturecoordinates set, but no texture defined in the .mtl-File!");
				else {
					Texture texture = new Texture(renderer, texPaths.get(0),
							ImageType.UNKNOWN);
					model.useTexture(texture);
				}
			}

			if (renderer.settings.mappingMode != 0) {
				if (bumpPaths.size() == 0)
					System.out
							.println("BumpMapping activated but no BumpMap defined in the .mtl-File!");
				else {
					Texture bumpMap = new Texture(renderer, bumpPaths.get(0),
							ImageType.UNKNOWN);
					model.useBumpMap(bumpMap);
				}
			}

			if (materialImported) {
				model.setMaterialAmbient(ambient.get(0));
				model.setMaterialDiffuse(diffuse.get(0));
				model.setMaterialSpecular(specular.get(0));
				model.setMaterialExponent(exponent.get(0)[0]);
			} else {
				System.out
						.println("No .mtl specified in the .obj-File. Setting materialvalues to standard");
				float[] matAmbient = { 0.4f, 0.4f, 0.4f, 1.0f };
				float[] matDiffuse = { 0.8f, 0.8f, 0.8f, 1.0f };
				float[] matSpecular = { 1.0f, 1.0f, 1.0f, 1.0f };
				float matExponent = 100;
				model.setMaterialAmbient(matAmbient);
				model.setMaterialDiffuse(matDiffuse);
				model.setMaterialSpecular(matSpecular);
				model.setMaterialExponent(matExponent);
			}

			if (model.getMaterialAmbient()[3] < 1.0f
					|| model.getMaterialDiffuse()[3] < 1.0f)
				model.setTransparent(true);

			// generate arrays and arrayLists to store vertices and indices
			// then read all the faces and build up the vertexBuffer
			String sFace;
			float[] vertexBuffer = new float[numVertices * 8];

			ArrayList<Short> indexBuffer = new ArrayList<Short>(numVertices * 3);
			StringTokenizer faceToken;
			int numFaces = 0;

			int OFFSET = 8;

			if (!model.normalsSet)
				OFFSET = 5;

			if (!model.textureCoordsSet)
				OFFSET = 6;

			if (!model.normalsSet && !model.textureCoordsSet)
				OFFSET = 3;

			token = new StringTokenizer(str);

			// put the first token in type
			type = token.nextToken();

			// Go through all remaining lines as between the faces of every
			// object there may be a break or material specified to use

			while (true) {

				// End of file reached
				if (str == null)
					break;

				// Skip all comments and blank lines
				while (str.length() == 0 || str.charAt(0) == '#') {
					str = in.readLine();
					if (str == null)
						return false;
				}

				token = new StringTokenizer(str);

				// put the first token in token
				type = token.nextToken();

				// get the next line
				str = in.readLine();
				token = new StringTokenizer(str);

				// put the first token in type
				type = token.nextToken();

				while (!type.equals("f")) {
					// get the next line
					str = in.readLine();
					token = new StringTokenizer(str);

					// put the first token in type
					type = token.nextToken();
				}

				if (type.equals("f")) {
					while (type.equals("f")) {
						// for each line: f vx/vtx/vnx vy/vty/vny vz/vtz/vnz
						// into the vertexBuffer
						for (int i = 0; i < 3; i++) {
							sFace = token.nextToken();

							// Tokenizer to ignore the /- which separates the
							// values
							faceToken = new StringTokenizer(sFace, "/");
							int indexVertex = Integer.parseInt(faceToken
									.nextToken()) - 1;

							int indexTextureCoordinate = 0;

							if (model.textureCoordsSet) {
								indexTextureCoordinate = Integer
										.parseInt(faceToken.nextToken()) - 1;
							}

							int indexNormal = 0;

							if (model.normalsSet) {
								indexNormal = Integer.parseInt(faceToken
										.nextToken()) - 1;
							}

							// Add 0 to numVertices to indexBuffer
							indexBuffer.add((short) indexVertex);

							// Add x, y, z to the vertexBuffer
							vertexBuffer[indexVertex * OFFSET] = vertexList
									.get(indexVertex * 3);
							vertexBuffer[indexVertex * OFFSET + 1] = vertexList
									.get(indexVertex * 3 + 1);
							vertexBuffer[indexVertex * OFFSET + 2] = vertexList
									.get(indexVertex * 3 + 2);
							
							if (model.normalsSet) {
								// Add nx, ny, nz to the vertexBuffer
								vertexBuffer[indexVertex * OFFSET
										+ NORMAL_OFFSET] = -normalList
										.get(indexNormal * 3);
								vertexBuffer[indexVertex * OFFSET
										+ NORMAL_OFFSET + 1] = -normalList
										.get(indexNormal * 3 + 1);
								vertexBuffer[indexVertex * OFFSET
										+ NORMAL_OFFSET + 2] = -normalList
										.get(indexNormal * 3 + 2);
							}

							if (model.textureCoordsSet) {
								// Add u, v to the vertexBuffer
								vertexBuffer[indexVertex * OFFSET
										+ TEXTURE_OFFSET] = textureCoordinateList
										.get(indexTextureCoordinate * 2);
								// Negative y-coordinate!
								vertexBuffer[indexVertex * OFFSET
										+ TEXTURE_OFFSET + 1] = -textureCoordinateList
										.get(indexTextureCoordinate * 2 + 1);
							}
						}

						// get the next line
						str = in.readLine();

						if (str == null)
							break;

						// If the end of the file is reached, finish the import
						if (str.length() == 0) {
							break;
						}

						// put the first token to token
						token = new StringTokenizer(str);
						type = token.nextToken();
						numFaces++;
					}

				}
			}

			model.vertices = vertexBuffer;

			// Copy all indices to indices
			indexBuffer.trimToSize();
			model.indices = new short[indexBuffer.size()];
			for (int i = 0; i < indexBuffer.size(); i++)
				model.indices[i] = indexBuffer.get(i);

			// Put vertices, normals and texture coordinates into bVertexBuffer
			model.mesh.bVertexBuffer = ByteBuffer
					.allocateDirect(model.vertices.length * FLOAT_SIZE_BYTES)
					.order(ByteOrder.nativeOrder()).asFloatBuffer();
			model.mesh.bVertexBuffer.put(model.vertices);
			model.mesh.bVertexBuffer.position(0);

			// Put all indices into bIndexBuffer
			model.bIndexBuffer = ByteBuffer
					.allocateDirect(model.indices.length * SHORT_SIZE_BYTES)
					.order(ByteOrder.nativeOrder()).asShortBuffer();
			model.bIndexBuffer.put(model.indices);
			model.bIndexBuffer.position(0);

			// System.out.println("Vertices:");
			// for (int i = 0; i < model.mesh.bVertexBuffer.limit(); i++){
			// System.out.print(model.mesh.bVertexBuffer.get(i) + ", ");
			// }
			// System.out.println("---");
			// System.out.println("Indices:");
			// for (int i = 0; i < model.bIndexBuffer.limit(); i++){
			// System.out.print(model.bIndexBuffer.get(i) + ", ");
			// }
			// System.out.println("---");

			if (useVBO) {
				int[] buffers = new int[2];
				GLES20.glGenBuffers(2, buffers, 0);

				GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]);
				GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,
						model.mesh.bVertexBuffer.capacity() * FLOAT_SIZE_BYTES,
						model.mesh.bVertexBuffer, GLES20.GL_STATIC_DRAW);

				GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
				GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER,
						model.bIndexBuffer.capacity() * SHORT_SIZE_BYTES,
						model.bIndexBuffer, GLES20.GL_STATIC_DRAW);

				model.mesh.vBuffer = buffers[0];
				model.iBuffer = buffers[1];
				model.numIndices = model.bIndexBuffer.capacity();

				// System.out.println("Generating VBOs: " + model.vBuffer + ", "
				// + model.iBuffer + ". For " + model.numIndices +
				// " indices in object " + model.name + ".");
			}

			// Close the stream
			in.close();
			return true;
		} catch (Exception e) {
			System.out.println("Error while importing modelCustom " + resource
					+ ": " + e.toString());

			return false;
		}
	}

	// ////////////////////////////////////////////////////////
	// /Loads all materials and textures from a .mtl-File
	// ////////////////////////////////////////////////////////

	private void loadMaterialMTL(String materialPath, LinkedList<String> names,
			LinkedList<float[]> ambient, LinkedList<float[]> diffuse,
			LinkedList<float[]> specular, LinkedList<float[]> exponent,
			LinkedList<String> texPaths, LinkedList<String> bumpPaths,
			LinkedList<String> mapnsPaths, String target) {

		try {
			AssetManager assetManager = renderer.mContext.getAssets();
			InputStream inputStream = assetManager.open(materialPath);

			BufferedReader in = new BufferedReader(new InputStreamReader(
					inputStream));

			String str = in.readLine();

			while (true) {
				// Skip all comments and blank lines
				while (str.length() == 0 || str.charAt(0) == '#') {
					str = in.readLine();
					if (str == null)
						return;
				}

				// get the next line
				StringTokenizer token = new StringTokenizer(str);
				// put the first token in type
				String type = token.nextToken();

				if (type.equals("newmtl")) {
					String matName;
					if (!token.hasMoreTokens())
						matName = "noName";
					else
						matName = token.nextToken();
					names.add(matName);
				}

				str = in.readLine();
				token = new StringTokenizer(str);
				type = token.nextToken();

				// Get all values for this material until a new material begins,
				// or the file ends
				// values will stored in the following variables
				float[] matExponent = new float[1];
				float[] matAmbient = new float[4];
				float[] matDiffuse = new float[4];
				float[] matSpecular = new float[4];
				float dValue = 0;
				float NiValue = 0;
				String texPath = null;
				String mapBump = null;
				String mapNs = null;
				material: while (!type.equals("newmtl")) {
					if (type.equals("Ns")) {
						// Exponent
						String Ns = token.nextToken();
						float NsValue = new Float(Ns);
						matExponent = new float[1];
						matExponent[0] = NsValue;
					} else if (type.equals("Ka")) {
						// Ambient
						matAmbient = new float[4];
						String Ka = token.nextToken();
						matAmbient[0] = new Float(Ka);

						Ka = token.nextToken();
						matAmbient[1] = new Float(Ka);

						Ka = token.nextToken();
						matAmbient[2] = new Float(Ka);
					} else if (type.equals("Kd")) {
						// Diffuse
						matDiffuse = new float[4];
						String Kd = token.nextToken();
						matDiffuse[0] = new Float(Kd);

						Kd = token.nextToken();
						matDiffuse[1] = new Float(Kd);

						Kd = token.nextToken();
						matDiffuse[2] = new Float(Kd);
					} else if (type.equals("Ks")) {
						// Specular
						matSpecular = new float[4];
						String Ks = token.nextToken();
						matSpecular[0] = new Float(Ks);

						Ks = token.nextToken();
						matSpecular[1] = new Float(Ks);

						Ks = token.nextToken();
						matSpecular[2] = new Float(Ks);
					} else if (type.equals("Ni")) {
						// Ni (optical density) - not used at the moment
						String Ni = token.nextToken();
						NiValue = new Float(Ni);
					} else if (type.equals("d")) {
						// d (Transparency)
						String d = token.nextToken();
						dValue = new Float(d);
					} else if (type.equals("map_Kd")) {
						// Colortexture
						texPath = token.nextToken();
						int pos = 0;
						for (int i = 0; i < texPath.length(); i++) {
							if (texPath.charAt(i) == '/') {
								pos = i;
							}
						}

						texPath = "textures/" + texPath.substring(pos + 1);
					} else if (type.equals("map_Bump")) {
						// Bumpmap
						mapBump = token.nextToken();

						int pos = 0;
						for (int i = 0; i < mapBump.length(); i++) {
							if (mapBump.charAt(i) == '/') {
								pos = i;
							}
						}

						mapBump = "textures/" + mapBump.substring(pos + 1);
					} else if (type.equals("map_Ns")) {
						mapNs = token.nextToken();

						int pos = 0;
						for (int i = 0; i < mapNs.length(); i++) {
							if (mapNs.charAt(i) == '/') {
								pos = i;
							}
						}

						mapNs = "textures/" + mapNs.substring(pos + 1);
					} else if (type.equals("illum")) {
						// Illumination-mode (unused)
					}

					str = in.readLine();
					if (str == null)
						break;
					token = new StringTokenizer(str);

					while (str.length() == 0 || !token.hasMoreElements()) {
						str = in.readLine();
						if (str == null)
							break material;
						token = new StringTokenizer(str);
					}
					type = token.nextToken();
				}

				// Set alpha-Value of the material-values
				exponent.add(matExponent);
				matAmbient[3] = dValue;
				matDiffuse[3] = dValue;
				matSpecular[3] = dValue;

				ambient.add(matAmbient);
				diffuse.add(matDiffuse);
				specular.add(matSpecular);

				if (texPath != null)
					texPaths.add(texPath);

				if (mapNs != null)
					mapnsPaths.add(mapNs);

				if (mapBump != null)
					bumpPaths.add(mapBump);

				// Check for end-of-file
				if (str == null) 
					return;
				
			}

		} catch (Exception e) {
			System.out.println("Error while importing " + materialPath + ": "
					+ e.toString());
			return;
		}

	}

	// ////////////////////////////////////////////////////////
	// /Loads a texture from given path into the system and returns the
	// OpenGL-ID
	// ////////////////////////////////////////////////////////

	public int loadTextureFromFile(String path, Context context, int ID) {

		// Test if the texture is allready in the system and if so return this
		// instead of loading another time
		for (int i = 0; i < this.loadedTextures.size(); i++) {
			if (path.equals(this.loadedTextures.get(i).resourcePath)) {
				System.out
						.println("Texture "
								+ path
								+ " already loaded, returning reference to this Texture.");
				return this.loadedTextures.get(i).ID;
			}
		}

		Bitmap bitmap = null;

		try {
			AssetManager assetManager = context.getAssets();
			InputStream inputStream = assetManager.open(path);

			bitmap = BitmapFactory.decodeStream(inputStream);

			inputStream.close();

		} catch (IOException e) {
			System.out.println("Error while importing " + path + ": "
					+ e.toString());
			return -1;
		}

		int[] IDs = new int[1];

		GLES20.glGenTextures(1, IDs, 0);

		ID = IDs[0];

		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, ID);

		GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
				GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
		GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
				GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

		GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
				GLES20.GL_REPEAT);
		GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
				GLES20.GL_REPEAT);

		GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
		bitmap.recycle();

		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

		return ID;
	}

	// ////////////////////////////////////////////////////////
	// /Loads a group of objects from given resource with all their materials,
	// textures etc.
	// ////////////////////////////////////////////////////////

	public boolean loadModelGroupFromOBJ(String resource,
			ModelGroup modelGroup, boolean useVBO) {

		for (ModelGroup m : loadedModelGroups) {
			if (m.path.equals(resource)) {
				System.out
						.println("ModelGroup "
								+ resource
								+ " allready loaded. Generating duplicate without re-importing. Better use identifiers for multiple instances.");
				modelGroup.models = m.models;
				modelGroup.name = m.name;
				return true;
			}
		}

		loadedModelGroups.add(modelGroup);

		try {

			AssetManager assetManager = renderer.mContext.getAssets();
			InputStream inputStream = assetManager.open(resource);
			ModelCustom firstModel = new ModelCustom();
			modelGroup.models.add(firstModel);

			BufferedReader in = new BufferedReader(new InputStreamReader(
					inputStream));

			LinkedList<String> names = new LinkedList<String>();
			LinkedList<float[]> ambient = new LinkedList<float[]>();
			LinkedList<float[]> diffuse = new LinkedList<float[]>();
			LinkedList<float[]> specular = new LinkedList<float[]>();
			LinkedList<float[]> exponent = new LinkedList<float[]>();
			LinkedList<String> texPaths = new LinkedList<String>();
			LinkedList<String> bumpPaths = new LinkedList<String>();
			LinkedList<String> mapnsPaths = new LinkedList<String>();

			String str = in.readLine();

			// Skip all comments and blank lines
			while (str.length() == 0 || str.charAt(0) == '#') {
				str = in.readLine();
				if (str == null)
					return false;
			}

			boolean materialImported = false;

			if (str.charAt(0) == 'm') {
				// Get the material path
				String target = resource;

				int pos = 0;
				for (int i = 0; i < resource.length(); i++) {
					if (resource.charAt(i) == '/') {
						pos = i;
					}
				}

				String path = resource.substring(0, pos);

				String materialPath = path + "/" + str.substring(7);

				this.loadMaterialMTL(materialPath, names, ambient, diffuse,
						specular, exponent, texPaths, bumpPaths, mapnsPaths,
						target);
				
				materialImported = true;

				str = in.readLine();
			}

			if (str.charAt(0) == 'o' || str.charAt(0) == 'g') {
				// Get the object name
				String objectName = str.substring(2);
				modelGroup.name = objectName;
			}

			// skip everything until first vertex occures
			while (str.charAt(0) != 'v') {
				// get the next line
				str = in.readLine();
				if (str == null)
					return false;
			}

			// lists to store all vertices, textureCoordinates and normals
			LinkedList<Float> vertexList = new LinkedList<Float>();
			LinkedList<Float> textureCoordinateList = new LinkedList<Float>();
			ArrayList<Float> normalList = new ArrayList<Float>(0);

			StringTokenizer token = new StringTokenizer(str);
			String type = token.nextToken();

			int numVertices = 0;
			while (type.equals("v")) {

				// Add x, y, z to vertexList
				vertexList.add(Float.parseFloat(token.nextToken()));
				vertexList.add(Float.parseFloat(token.nextToken()));
				vertexList.add(Float.parseFloat(token.nextToken()));

				// get the next line
				str = in.readLine();
				if (str.length() == 0)
					break;

				// put the fist token in token
				token = new StringTokenizer(str);
				type = token.nextToken();
				numVertices++;
			}

			// Skip all comments and blank lines
			while (str.length() == 0 || str.charAt(0) == '#') {
				str = in.readLine();
				if (str == null)
					return false;
			}

			token = new StringTokenizer(str);
			type = token.nextToken();

			int numTexCoords = 0;
			if (type.equals("vt")) {

				// Model uses texture
				firstModel.textureCoordsSet = true;

				while (type.equals("vt")) {

					// Add u, v to textureCoordinateList
					textureCoordinateList.add(Float.parseFloat(token
							.nextToken()));
					textureCoordinateList.add(Float.parseFloat(token
							.nextToken()));

					// get the next line
					str = in.readLine();
					if (str.length() == 0)
						break;

					token = new StringTokenizer(str);
					// put the first token in token
					type = token.nextToken();
					numTexCoords++;
				}
			}

			// Skip all comments and blank lines
			while (str.length() == 0 || str.charAt(0) == '#') {
				str = in.readLine();
				if (str == null)
					return false;
			}

			token = new StringTokenizer(str);
			// put the first token in token
			type = token.nextToken();

			if (type.equals("vn")) {

				firstModel.normalsSet = true;

				while (type.equals("vn")) {
					// Add nx, ny, nz to normalList
					normalList.add(Float.parseFloat(token.nextToken()));
					normalList.add(Float.parseFloat(token.nextToken()));
					normalList.add(Float.parseFloat(token.nextToken()));

					// get the next line
					str = in.readLine();
					if (str.length() == 0)
						break;

					token = new StringTokenizer(str);
					// put the first token in token
					type = token.nextToken();
				}
			}

			// Skip all comments and blank lines
			while (str.length() == 0 || str.charAt(0) == '#') {
				str = in.readLine();
				if (str == null)
					return false;
			}

			// now read all the faces
			String sFace;
			ArrayList<Float> vertexBuffer = new ArrayList<Float>(
					numVertices * 8);
			for (int i = 0; i < numVertices * 8; i++) {
				vertexBuffer.add(0.0f);
			}

			ArrayList<Short> indexBuffer = new ArrayList<Short>(numVertices * 3);
			StringTokenizer faceToken;
			int numFaces = 0;

			int OFFSET = 8;

			if (!firstModel.normalsSet)
				OFFSET = 5;

			if (!firstModel.textureCoordsSet)
				OFFSET = 6;

			if (!firstModel.normalsSet && !firstModel.textureCoordsSet)
				OFFSET = 3;

			int count = 0;
			// Uberfaceschleife
			while (true) {

				// End of file reached
				if (str == null)
					break;

				if (count > 10000)
					break;

				// Skip all comments and blank lines
				while (str.length() == 0 || str.charAt(0) == '#') {
					str = in.readLine();
					if (str == null)
						return false;
				}

				indexBuffer = new ArrayList<Short>(numVertices * 3);
				ModelCustom model;
				if (count == 0) {
					model = firstModel;
				} else {
					model = new ModelCustom();
					modelGroup.models.add(model);
				}
				model.textureCoordsSet = firstModel.textureCoordsSet;
				model.normalsSet = firstModel.normalsSet;
				model.textureSet = firstModel.textureSet;

				count++;

				token = new StringTokenizer(str);

				// put the first token in token
				type = token.nextToken();

				// Material for this model (includes texture, bumpmap texture,
				// etc.)
				if (type.equals("usemtl")) {
					String materialName = str.substring(7);
					if (materialName.length() == 0)
						materialName = "noName";

					model.name = materialName;

					int k = 0;

					for (int i = 0; i < names.size(); i++) {
						if (materialName.equals(names.get(i))) {
							k = i;
							break;
						}
					}

					Texture texture = new Texture(renderer, texPaths.get(k),
							ImageType.UNKNOWN);
					model.useTexture(texture);

					if (renderer.settings.mappingMode != 0) {
						if (bumpPaths.size() == 0)
							System.out
									.println("BumpMapping activated but no BumpMap defined in the .mtl-File!");
						else {
							Texture bumpMap = new Texture(renderer,
									bumpPaths.get(k), ImageType.UNKNOWN);
							model.useBumpMap(bumpMap);
						}
					}
					if (materialImported) {
						model.setMaterialAmbient(ambient.get(k));
						model.setMaterialDiffuse(diffuse.get(k));
						model.setMaterialSpecular(specular.get(k));
						model.setMaterialExponent(exponent.get(k)[0]);
					} else {
						float[] matAmbient = { 0.4f, 0.4f, 0.4f, 1.0f };
						float[] matDiffuse = { 0.8f, 0.8f, 0.8f, 1.0f };
						float[] matSpecular = { 1.0f, 1.0f, 1.0f, 1.0f };
						float matExponent = 100;
						model.setMaterialAmbient(matAmbient);
						model.setMaterialDiffuse(matDiffuse);
						model.setMaterialSpecular(matSpecular);
						model.setMaterialExponent(matExponent);
					}

					if (model.getMaterialAmbient()[3] < 1.0f || model.getMaterialDiffuse()[3] < 1.0f)
						model.setTransparent(true);

					// get the next line
					str = in.readLine();
					token = new StringTokenizer(str);

					// put the first token in token
					type = token.nextToken();
				}

				while (!type.equals("f")) {
					// get the next line
					str = in.readLine();
					token = new StringTokenizer(str);

					// put the first token in token
					type = token.nextToken();
				}

				if (type.equals("f")) {
					while (type.equals("f")) {
						// for each line: f vx/vtx/vnx vy/vty/vny vz/vtz/vnz
						// into the vertexBuffer
						for (int i = 0; i < 3; i++) {

							sFace = token.nextToken();

							// Tokenizer to ignore the /- which seperates the
							// values
							faceToken = new StringTokenizer(sFace, "/");
							int indexVertex = Integer.parseInt(faceToken
									.nextToken()) - 1;

							int indexTextureCoordinate = 0;

							if (model.textureCoordsSet) {
								indexTextureCoordinate = Integer
										.parseInt(faceToken.nextToken()) - 1;
							}

							int indexNormal = 0;

							if (model.normalsSet) {
								indexNormal = Integer.parseInt(faceToken
										.nextToken()) - 1;
							}

							// Add 0 to numVertices to indexBuffer
							indexBuffer.add((short) indexVertex);

							// Add x, y, z to the vertexBuffer
							vertexBuffer.set(indexVertex * OFFSET,
									vertexList.get(indexVertex * 3));
							vertexBuffer.set(indexVertex * OFFSET + 1,
									vertexList.get(indexVertex * 3 + 1));
							vertexBuffer.set(indexVertex * OFFSET + 2,
									vertexList.get(indexVertex * 3 + 2));

							if (model.normalsSet) {
								// Add nx, ny, nz to the vertexBuffer
								vertexBuffer.set(indexVertex * OFFSET
										+ NORMAL_OFFSET,
										-normalList.get(indexNormal * 3));
								vertexBuffer.set(indexVertex * OFFSET
										+ NORMAL_OFFSET + 1,
										-normalList.get(indexNormal * 3 + 1));
								vertexBuffer.set(indexVertex * OFFSET
										+ NORMAL_OFFSET + 2,
										-normalList.get(indexNormal * 3 + 2));
							}

							if (model.textureCoordsSet) {
								// Add u, v to the vertexBuffer
								vertexBuffer.set(indexVertex * OFFSET
										+ TEXTURE_OFFSET, textureCoordinateList
										.get(indexTextureCoordinate * 2));
								vertexBuffer
										.set(indexVertex * OFFSET
												+ TEXTURE_OFFSET + 1,
												-textureCoordinateList
														.get(indexTextureCoordinate * 2 + 1));
							}
						}

						// get the next line
						str = in.readLine();

						if (str == null)
							break;

						// If the end of the file is reached, finish the import
						if (str.length() == 0) {
							break;
						}

						// put the first token to token
						token = new StringTokenizer(str);
						type = token.nextToken();
						numFaces++;
					}
				}

				// Copy all indices to indices
				indexBuffer.trimToSize();
				model.indices = new short[indexBuffer.size()];
				for (int i = 0; i < indexBuffer.size(); i++)
					model.indices[i] = indexBuffer.get(i);

				// Put all indices into bIndexBuffer
				model.bIndexBuffer = ByteBuffer
						.allocateDirect(model.indices.length * SHORT_SIZE_BYTES)
						.order(ByteOrder.nativeOrder()).asShortBuffer();
				model.bIndexBuffer.put(model.indices);
				model.bIndexBuffer.position(0);

				if (useVBO) {
					int[] buffers = new int[1];
					GLES20.glGenBuffers(1, buffers, 0);

					GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER,
							buffers[0]);
					GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER,
							model.bIndexBuffer.capacity() * SHORT_SIZE_BYTES,
							model.bIndexBuffer, GLES20.GL_STATIC_DRAW);

					model.mesh = firstModel.mesh;
					model.iBuffer = buffers[0];
					model.numIndices = model.bIndexBuffer.capacity();

				}
			}

			vertexBuffer.trimToSize();

			float[] vertices = new float[vertexBuffer.size()];

			// Copy x, y, z, nx, ny, nz, u, v to vertices[]
			for (int i = 0; i < vertexBuffer.size(); i++)
				vertices[i] = vertexBuffer.get(i);

			// Put vertices, normals and texture coordinates into bVertexBuffer
			firstModel.mesh.bVertexBuffer = ByteBuffer
					.allocateDirect(vertices.length * FLOAT_SIZE_BYTES)
					.order(ByteOrder.nativeOrder()).asFloatBuffer();
			firstModel.mesh.bVertexBuffer.put(vertices);
			firstModel.mesh.bVertexBuffer.position(0);

			if (useVBO) {
				int[] buffers = new int[1];
				GLES20.glGenBuffers(1, buffers, 0);

				GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]);
				GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,
						firstModel.mesh.bVertexBuffer.capacity()
								* FLOAT_SIZE_BYTES,
						firstModel.mesh.bVertexBuffer, GLES20.GL_STATIC_DRAW);
				firstModel.mesh.vBuffer = buffers[0];
			}

			for (int i = 1; i < modelGroup.models.size(); i++) {
				modelGroup.models.get(i).mesh = firstModel.mesh;
				modelGroup.models.get(i).normalsSet = firstModel.normalsSet;
				modelGroup.models.get(i).textureCoordsSet = firstModel.textureCoordsSet;
			}

			// Close the stream
			in.close();
			return true;

		} catch (Exception e) {
			System.out.println("Error while importing " + resource + ": "
					+ e.toString());
			return false;
		}
	}

}
